#include <drivers/gpio.h>
#include <drivers/platform.h>
#include <kernel/syslog.h>
#include <list.h>
#include <types.h>
#include <lib/eb_string.h>
#include <stm32f10x_reg.h>
#include <irq_num.h>
#include <drivers/irq.h>


#define GPIO_REG_SIZE            (0x400)
#define GPIOX_BASE(x)            ((APB2PERIPH_BASE + 0x0800) + GPIO_REG_SIZE * (x))
           
#define GPIO_DATA_REG(x)         (GPIOX_BASE(x) + 0x0C)
#define GPIO_BSRR_REG(x)         (GPIOX_BASE(x) + 0x10)

#define EXTI_IMR_REG             EXTI_BASE
#define EXTI_EMR_REG             (EXTI_BASE + 0x04)
#define EXTI_RTSR_REG            (EXTI_BASE + 0x08)
#define EXTI_FTSR_REG            (EXTI_BASE + 0x0C)

#define EXTI_AFIO_CR_REG(x)      (AFIO_BASE + 0x08 + (x)*0x04)


static void stm32f10x_set_mux(uint16_t pin, uint8_t mux)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) / 8;

    volatile uint32_t *port_addr = (volatile uint32_t *)(GPIOX_BASE(port) + pin_bit * 0x04);

    *port_addr &= ~( 0xF << mode_bit);
    *port_addr |= ( mux << mode_bit);
}


static void stm32f10x_set_dir(uint16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) / 8;

    volatile uint32_t *port_addr = (volatile uint32_t *)(GPIOX_BASE(port) + pin_bit * 0x04);

    if(flag) {
        *port_addr |= (3 << mode_bit);
    }
    else {
        *port_addr &= (~(3 << mode_bit));
    }
}


static void stm32f10x_set_drv(uint16_t pin, uint8_t drv)
{
    //no need to do
}


static void stm32f10x_set_value(uint16_t pin, uint8_t value)
{
    uint32_t port = IO_TO_PORT(pin);
    volatile uint32_t *port_addr = (volatile uint32_t *)GPIO_BSRR_REG(port);

    if(value) {
        *port_addr |= (1 << IO_TO_PIN(pin));
    }
    else {
        *port_addr |= (1 << (IO_TO_PIN(pin) + 16));
    }
}


static int stm32f10x_get_value(uint16_t pin)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t num = IO_TO_PIN(pin);
    volatile uint32_t *port_addr = (volatile uint32_t *)GPIO_DATA_REG(port);

    return ((*port_addr) & (1 << num)) ? true : false;
}


static int stm32f10x_set_exti(uint16_t pin, int flag)
{
    int gpio_pin = IO_TO_PIN(pin);
    uint32_t gpio_port = IO_TO_PORT(pin);

    *((volatile uint32_t *)EXTI_IMR_REG) |= (1 << gpio_pin);

    if(flag & IRQF_RISING || flag & IRQF_HIGH) {
        *((volatile uint32_t *)EXTI_RTSR_REG) |= (1 << gpio_pin);
    }

    if(flag & IRQF_FALLING || flag & IRQF_LOW) {
        *((volatile uint32_t *)EXTI_FTSR_REG) |= (1 << gpio_pin);
    }

    //set the mode to reset status
    stm32f10x_set_mux(pin, 0x4);

    //set gpio exti map port 
    volatile uint32_t *afiocr_addr = (volatile uint32_t *)(EXTI_AFIO_CR_REG(gpio_pin >> 2));
    *afiocr_addr &= (~(0xF << ((gpio_pin & 3) * 4)));
    *afiocr_addr |= (gpio_port << ((gpio_pin & 3) * 4));

    switch(gpio_pin) {
        case 0:  return EXTI0_IRQNUM;
        case 1: return EXTI1_IRQNUM;
        case 2: return EXTI2_IRQNUM;
        case 3: return EXTI3_IRQNUM;
        case 4: return EXTI4_IRQNUM;
        case 5 ... 9: return EXTI9_5_IRQNUM;
        case 10 ... 15: return EXTI15_10_IRQNUM;
        default: return -1;
    }

    return -1;
}


struct gpio_ops stm32f10x_ops = {
    .set_mux = stm32f10x_set_mux,
    .set_dir = stm32f10x_set_dir,
    .set_drv = stm32f10x_set_drv,
    .set_value = stm32f10x_set_value,
    .get_value = stm32f10x_get_value,
    .set_exti = stm32f10x_set_exti,
};


struct gpio_desc stm32f10x_gpio = {
    .name = "stm32f10x-gpio",
    .npins = 228,
    .ops = &stm32f10x_ops,
};


static int stm32f10x_gpio_register(void)
{
    gpio_desc_register(&stm32f10x_gpio);
    return 0;
}


base_initcall(stm32f10x_gpio_register, 0);
